<?php
// +------------------------------------------------------------------
// | Cls-Token [ USER LOGIN AUTHENTICATION TOKEN ]
// | Copyright (c) 2024 All rights reserved.
// | Based on ThinkPHP 6 | 8
// | Licensed Mulan PSL v2 ( http://license.coscl.org.cn/MulanPSL2 )
// | Author: CLS <422064377>
// | CreateDate: 2024-07-12
// +------------------------------------------------------------------

namespace chleniang\ClsToken\driver;

use chleniang\ClsToken\Driver;
use chleniang\ClsToken\exception\ClsTokenDBException;
use chleniang\ClsToken\exception\ClsTokenException;
use think\facade\Db;

class Database extends Driver
{

    /**
     * 当前驱动配置参数 (基础配置参数在属性 $base_options)
     * @var array
     */
    protected array $options = [
        "connection"  => "",
        "token_table" => "cls_token",
    ];

    public function __construct(array $options = [])
    {
        parent::__construct($options);

        $this->handler = $this->createDbQuery();
    }

    /**
     * @throws ClsTokenException
     */
    public function set(array $tokenData)
    {
        if (
            !empty($tokenData['refresh_token'])
            && (1 > $tokenData['refresh_expire'])
        ) {
            throw new ClsTokenException([
                'msg' => 'Token expire time invalid.',
            ]);
        }

        $insertData            = $tokenData;
        $insertData['ex_info'] = json_encode($tokenData['ex_info'], JSON_UNESCAPED_UNICODE);

        /* ********** 事务开始 ********** */
        Db::startTrans();
        try {
            $info = $this->handler
                ->field(true)
                ->lock(true)
                ->where('user_identifier', '=', $insertData['user_identifier'])
                ->find();
            if (empty($info)) {
                $this->handler->insert($insertData);
            }
            else {
                $this->handler
                    ->lock(false)
                    ->where('user_identifier', '=', $insertData['user_identifier'])
                    ->update($insertData);
            }

            Db::commit();
        }
        catch (\Exception $e) {
            Db::rollback();
            throw new ClsTokenException([
                'msg' => 'Token database insert ERROR:' . $e->getMessage(),
            ]);
        } /* ********** 事务结束 ********** */

    }

    /**
     * 获取指定用户的token记录数据
     *      没找到返回空数组
     *
     * @param int|string $userIdentifier 用户标识(id/uuid)
     *
     * @return array
     */
    public function get(int|string $userIdentifier)
    {
        $info = $this->getOriginData($userIdentifier);

        $info['expire'] = $this->base_options['expire'];
        if (!empty($info['refresh_token'])) {
            $info['refresh_expire'] = $this->base_options['refresh_expire'];
        }

        return $info;
    }

    /**
     * @throws ClsTokenException
     */
    public function delete(int|string $userIdentifier)
    {
        // $clearData = [
        //     'token'          => '',
        //     'expire'         => 0,
        //     'refresh_token'  => '',
        //     'refresh_expire' => 0,
        //     'update_time'    => 0,
        //     'user_agent'     => '',
        //     'ip'             => '',
        //     'ex_info'        => json_encode([]),
        // ];
        try {
            // db->delete()返回影响数据的条数，没有删除任何数据返回 0
            $res = $this->handler
                ->where('user_identifier', '=', (string)$userIdentifier)
                ->delete();
            return $res > 0;
        }
        catch (\Exception $e) {
            throw new ClsTokenException([
                'msg' => 'Token database delete ERROR:' . $e->getMessage(),
            ]);
        }
    }

    /**
     * 指定token记录表
     *      (本database驱动特有方法)
     *
     * @param string $tableName token记录表名(不含前缀)
     *
     * @return $this
     */
    public function setTokenTable(string $tableName)
    {
        $this->options['token_table'] = $tableName;

        $this->handler = $this->createDbQuery();

        return $this;
    }

    protected function getOriginData(int|string $userIdentifier)
    {
        $info = $this->handler
            ->field(true)
            ->where('user_identifier', '=', (string)$userIdentifier)
            ->findOrEmpty();
        if (!empty($info['ex_info'])) {
            $info['ex_info'] = json_decode($info['ex_info'], true);
        }
        elseif (isset($info['ex_info'])) {
            $info['ex_info'] = [];
        }

        return $info;
    }

    private function createDbQuery(): \think\db\BaseQuery
    {
        $dbConnectionName = null;
        if (!empty($this->options['connection'])) {
            $dbConf           = Db::getConfig();
            $dbConnectionKeys = array_keys($dbConf['connections']);
            if (!in_array($this->options['connection'], $dbConnectionKeys)) {
                throw new ClsTokenDBException([
                    "msg" => "database connection \"{$this->options['connection']}\" not found.",
                ]);
            }
            $dbConnectionName = $this->options['connection'];
        }
        return Db::connect($dbConnectionName)
            ->name($this->options['token_table']);
    }

}